﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace NetworkLibrary
{

    /// <summary>
    /// 实现套接字服务器的连接逻辑。
    /// 在接受连接后，从客户机读取的所有数据都被发送回客户机。
    /// 读取并回传给客户端模式，直到客户端断开连接。
    /// </summary>
    public class TcpService
    {
        //读，写(不要为接受分配缓冲区空间)
        private const int _opsToPreAlloc = 2;
        // 样本被设计为同时处理的最大连接数
        private int _connectSize;
        // 用于每个套接字I/O操作的缓冲区大小
        private int _bufferSize;
        // 表示用于所有套接字操作的大型可重用缓冲区集
        private Buffer _buffer;
        // 用于监听可重用SocketAsyncEventArgs对象的传入连接请求池，用于写、读和接受套接字操作的套接字
        private Socket _listenSocket;
        // 异步事件对象池
        private SocketEventArgsPool _eventArgsPool;
        // 链接列表
        private IndexSet<TcpConnect> _connects;
        // 连接到服务器的客户端总数
        int _connectedCount;
        Semaphore _maxAcceptedClientCount;
        private ISynchronizationContext _synchronizationContext;
        private SocketAsyncEventArgs _inEventArgs;
        public string Host { get; private set; }
        public int Port { get; private set; }
        /// <summary>
        /// 创建一个未初始化的服务器实例。要启动服务器监听连接请求，先调用Init方法，然后调用start方法
        /// </summary>
        /// <param name="connectSize">样本被设计为同时处理的最大连接数</param>
        /// <param name="buffSize">用于每个套接字I/O操作的缓冲区大小</param>
        public TcpService(int connectSize, int buffSize, ISynchronizationContext synchronizationContext)
        {
            _connectedCount = 0;
            _connectSize = connectSize;
            _bufferSize = buffSize;
            _buffer = new Buffer(buffSize, connectSize * _opsToPreAlloc);
            _eventArgsPool = new SocketEventArgsPool();
            _connects = new IndexSet<TcpConnect>();
            _maxAcceptedClientCount = new Semaphore(connectSize, connectSize);
            _inEventArgs = new SocketAsyncEventArgs();
            _inEventArgs.Completed += OnEventArgsComplete;
            _synchronizationContext = synchronizationContext;
        }

        /// <summary>
        /// 启动服务器，以便它侦听传入的连接请求。
        /// </summary>
        /// <param name="localEndPoint">服务器将侦听连接请求的端点</param>
        public void Start(int port, string ip = null)
        {
            Port = port;
            var ipAddress = ip == null ? IPAddress.Any : IPAddress.Parse(ip);
            var localEndPoint = new IPEndPoint(ipAddress, port);
            _listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            _listenSocket.NoDelay = true;
            _listenSocket.Bind(localEndPoint);
            _listenSocket.Listen(100);
            BeginAccept();
        }

        /// <summary>
        /// 开始操作以接受来自客户机的连接请求
        /// </summary>
        /// <param name="acceptEventArg">在服务器侦听套接字上发出接受操作时要使用的上下文对象</param>
        private void BeginAccept()
        {
            _inEventArgs.AcceptSocket = null;
            _maxAcceptedClientCount.WaitOne();
            if (!_listenSocket.AcceptAsync(_inEventArgs))
            {
                OnAcceptComplete(_inEventArgs.SocketError, _inEventArgs.AcceptSocket);
            }
        }

        /// <summary>
        /// 这个方法是与Socket相关联的回调方法。AcceptAsync操作，并在接受操作完成时调用
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void EndAccept(object sender, SocketAsyncEventArgs e)
        {
            Interlocked.Increment(ref _connectedCount);
            SocketAsyncEventArgs readEventArgs = _eventArgsPool.Spawn();
            SocketAsyncEventArgs writeEventArgs = _eventArgsPool.Spawn();
            _buffer.SetBuffer(readEventArgs);
            _buffer.SetBuffer(writeEventArgs);
            var connect = _connects.Push((id) => new TcpConnect((ushort)id));
            connect.Start(e.AcceptSocket, e.RemoteEndPoint as IPEndPoint, readEventArgs, writeEventArgs, () =>
            {
                _buffer.FreeBuffer(readEventArgs);
                _buffer.FreeBuffer(writeEventArgs);
            });
            BeginAccept();
        }

        private void OnEventArgsComplete(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Accept:
                    SocketError socketError = e.SocketError;
                    Socket acceptSocket = e.AcceptSocket;
                    this._synchronizationContext.SyncCall(() => OnAcceptComplete(socketError, acceptSocket));
                    break;
                default:
                    throw new Exception($"socket error: {e.LastOperation}");
            }
        }

        private void OnAcceptComplete(SocketError socketError, Socket acceptSocket)
        {
            if (this._listenSocket == null)
            {
                return;
            }
            // 开始新的accept
            BeginAccept();

            if (socketError != SocketError.Success)
            {
                // TODO 输出错误
                return;
            }

            try
            {
                var connect = _connects.Push((connId) => new TcpConnect((ushort)connId));
                OnAccept(connect.Id, connect);
            }
            catch (Exception exception)
            {
            }
        }
        protected virtual void OnAccept(ushort connId, TcpConnect connect)
        {

        }




        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            AsyncUserToken token = e.UserToken as AsyncUserToken;

            // close the socket associated with the client
            try
            {
                token.Socket.Shutdown(SocketShutdown.Send);
            }
            // throws if client process has already closed
            catch (Exception) { }
            token.Socket.Close();

            // decrement the counter keeping track of the total number of clients connected to the server
            Interlocked.Decrement(ref _connectedCount);

            // Free the SocketAsyncEventArg so they can be reused by another client
            _eventArgsPool.Push(e);

            _maxAcceptedClientCount.Release();
            Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", _connectedCount);
        }
    }
}
